home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 2.toast / pc / sample code / interapplication comm / folder watching / folder watcher fba / fbatask.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-06-23  |  15.2 KB  |  506 lines

  1. /*
  2.     File:        FBATask.c
  3.  
  4.     Contains:    
  5.  
  6.     Written by: Greg Sutton    
  7.  
  8.     Copyright:    Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
  9.  
  10.                 You may incorporate this Apple sample source code into your program(s) without
  11.                 restriction. This Apple sample source code has been provided "AS IS" and the
  12.                 responsibility for its operation is yours. You are not permitted to redistribute
  13.                 this Apple sample source code as "Apple sample source code" after having made
  14.                 changes. If you're going to re-distribute the source, we require that you make
  15.                 it clear in the source that the code was descended from Apple sample source
  16.                 code, but that you've made changes.
  17.  
  18.     Change History (most recent first):
  19.                 7/21/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
  20.                 
  21.  
  22. */
  23.  
  24.  
  25. #include "FBATask.h"
  26.  
  27. #include "FBA.h"
  28. #include "FBAAppleEvents.h"
  29. #include "FBALists.h"
  30.  
  31. #include <Errors.h>
  32. #include <Resources.h>
  33. #include <Processes.h>
  34. #include <TextUtils.h>
  35.  
  36.  
  37. typedef struct
  38. {
  39.     long                theState;        // We check for folder changes in stages - this is the state
  40.     unsigned long        theLastTime;    // The last time a volume check was initiated.
  41.     long                theResultIndex;    // For stpping through the results of the PBCatSearch().
  42.     FSSpec*                theInfoDir;        // FSSpec for a folder from PBCatSearch() results - this
  43.                                         //  has PBGetCatInfo() called for number of files.
  44. } SearchStateRec, *SearchStatePtr;
  45.  
  46.  
  47.     // Prototypes
  48. long                    DoSearchIdle( void );
  49. long                    DoWaitForCatSearch( void );
  50. long                    DoCheckCatSearchResults( void );
  51. long                    DoSearchNextVolume( void );
  52.  
  53. static unsigned long    GetAverageSleepTime( void );
  54. static OSErr            GetTargetApplicationCreator( OSType* theCreator );
  55. static Boolean            TargetApplicationRunning( ProcessSerialNumber* thePSN );
  56. static Boolean            GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN );
  57.  
  58. static void                StartCatSearch( WatchVolumePtr theVolPtr );
  59. static void                StartCatInfo( FSSpec* theSpec );
  60. static Boolean            CheckCatInfoResult( ProcessSerialNumber* thePSN );
  61.  
  62.  
  63.     // Constants
  64. const long        MaxFiles = 200;            // Maximum number of files received from PBCatSearch()
  65. const long        BufferSize = 16 * 1024;    // Buffer size for PBCatSearch()
  66. const long        DiskAccessSleep = 5;    // 5 ticks to WaitNextEvent() after calling an
  67.                                         //    asynchronous file access call.
  68. const long        MaxBatchEvents = 20;    // Limit the Apple events we send between
  69.                                         //  WaitNextEvent() calls.            
  70.  
  71. enum        // Values for theState of our SearchStateRec
  72. {
  73.     kSearchIdle = 0, kWaitForCatSearch, kCheckCatSearchResults, kSearchNextVolume
  74. };
  75.  
  76.  
  77.     // Globals
  78. SearchStateRec    gSearchState;            // Current state of search.
  79. CSParam            gCSParamRec;            // PBCatSearch() parameter block.
  80. CInfoPBRec        gSearchInfo1;            // Add these addresses to the parameter block
  81. CInfoPBRec        gSearchInfo2;            // for defining search space.
  82. CInfoPBRec        gCInfoPBRec;            // PBGetCatInfo() parameter block.
  83.  
  84. OSType            gTargetCreator;            // The creator type of the application
  85.                                         //  to send change events to.
  86.  
  87.  
  88. // Initialise all of the memory we shall use for calls to PBCatSearch().
  89. // We also set all the PBCatSearch() parameter block fields which
  90. // will not change.
  91.  
  92. Boolean        InitTask( void )
  93. {
  94.     OSErr        err;
  95.     Boolean        result = false;
  96.     
  97.             // Make sure we have a target application.
  98.  
  99.     err = GetTargetApplicationCreator( &gTargetCreator );
  100.     if ( noErr != err ) goto done;
  101.     
  102.             // Set up our global search state
  103.  
  104.     gSearchState.theState = kSearchIdle;
  105.     GetDateTime( &gSearchState.theLastTime );
  106.     gSearchState.theResultIndex = 0;
  107.     gSearchState.theInfoDir = NULL;
  108.  
  109.             // Set up our global PBCatSearch parameter block
  110.  
  111.     gCSParamRec.ioNamePtr = NULL;
  112.  
  113.     gCSParamRec.ioSearchInfo1 = &gSearchInfo1;
  114.     gCSParamRec.ioSearchInfo2 = &gSearchInfo2;
  115.     
  116.     gCSParamRec.ioMatchPtr = (FSSpecPtr)NewPtr( sizeof(FSSpec) * MaxFiles );  
  117.     if ( ! gCSParamRec.ioMatchPtr ) goto done;
  118.     gCSParamRec.ioReqMatchCount = MaxFiles;
  119.     
  120.     gCSParamRec.ioOptBuffer = NewPtr( BufferSize );
  121.     if ( ! gCSParamRec.ioOptBuffer ) goto done;
  122.     gCSParamRec.ioOptBufSize = BufferSize;
  123.  
  124.             // Set up PBGetCatInfo() parameter block
  125.  
  126.     gCInfoPBRec.dirInfo.ioResult = 0;
  127.     
  128.     result = true;
  129.     
  130. done:
  131.     return result;
  132. }
  133.  
  134.  
  135. // This routine coordinates the different stages of the search.
  136. // It returns the number of ticks for sleep in WaitNextEvent().
  137.  
  138. long    DoBackgroundTask( void )
  139. {
  140.     long    result;
  141.  
  142.     switch ( gSearchState.theState )
  143.     {
  144.         case kSearchIdle:                            // Check it is time, and we can run
  145.             result = DoSearchIdle( );                //  off another volume search.
  146.             break;
  147.             
  148.         case kWaitForCatSearch:                        // Make sure the PBCatSearch() completed
  149.             result = DoWaitForCatSearch( );            //  with no errors.
  150.             break;
  151.             
  152.         case kCheckCatSearchResults:                // Our PBCatSearch() on the directory
  153.             result = DoCheckCatSearchResults( );    //  modification dates has completed
  154.             break;                                    //  so act on the results.
  155.             
  156.         case kSearchNextVolume:                        // Set up the task for the next volume.
  157.             result = DoSearchNextVolume( );
  158.             break;
  159.     }
  160.  
  161.     return result;        // Result is in ticks
  162. }
  163.  
  164.  
  165. // Check whether it's time to start another search.
  166. // If it is then start a PBCatSearch.
  167. // If it isn't then return an updated time for WaitNextEvent().
  168.  
  169. long    DoSearchIdle( void )
  170. {
  171.     unsigned long    now;
  172.     long            result;
  173.     
  174.     if ( ! GetHeadVolumePtr( ) )
  175.         return GetAverageSleepTime( ) * 60;    // Time to WaitnextEvent() in ticks
  176.     
  177.     GetDateTime( &now );    // Time now in seconds
  178.     
  179.                 // Calculate time in seconds then multiply to ticks
  180.     result = (( gSearchState.theLastTime + GetAverageSleepTime( ) ) - now ) * 60;
  181.     
  182.                 // Time for another PBCatSearch() check
  183.     if ( result <= 0 )
  184.     {
  185.         StartCatSearch( GetHeadVolumePtr( ) );
  186.         
  187.         result = DiskAccessSleep;    // Give over some time to other applications but
  188.     }                                // we want to check the results of the search soon.
  189.  
  190.     return result;
  191. }
  192.  
  193.  
  194. // Check that the PBCatSearch() has completed. Also check the error returned
  195. // by the PBCatSearch() and act appropriately.
  196.  
  197. long    DoWaitForCatSearch( void )
  198. {
  199.     long            result;
  200.     
  201.     if ( gCSParamRec.ioResult >= 0 )    // The PBCatSearch() still hasn't finished
  202.         return DiskAccessSleep;            //  try again in a litte while.
  203.     
  204.  
  205.     switch ( gCSParamRec.ioResult )
  206.     {
  207.         case eofErr:                    // The search completed succesfully so
  208.             gSearchState.theState = kCheckCatSearchResults;
  209.             break;                        //  we can now look at the results.
  210.             
  211.         case catChangedErr:                // The catalog search as interrupted so results
  212.         case afpCatalogChanged:            //  could be miss some changes.
  213.             gSearchState.theState = kSearchIdle;    // The idle call will kick off
  214.             break;                                    //  another PBCatSearch() on the same
  215.                                                     //  volume straight away.
  216.  
  217.         default:                        // Some kind of problem with the search
  218.                                         //  try going onto the next volume.
  219.             gSearchState.theState = kSearchNextVolume;    
  220.     }
  221.     
  222.     
  223.     result = DoBackgroundTask( );        // Act on the result straight away.
  224.  
  225.     return result;
  226. }
  227.  
  228.  
  229. // The PBCatSearch() has completed - check the results in the parameter block.
  230. // This function 
  231.  
  232. long    DoCheckCatSearchResults( void )
  233. {
  234.     ProcessSerialNumber    aPSN;
  235.     FSSpec*                anFSSpecPtr;
  236.     WatchFolderPtr        aWatchFolderPtr;
  237.     long                eventsSent = 0;
  238.     long                result = 0;
  239.     
  240.     if ( TargetApplicationRunning( &aPSN ) )
  241.     {
  242.         if ( ! CheckCatInfoResult( &aPSN ) )
  243.             return DiskAccessSleep;            // Must still be waiting for the PBGetCatInfo()
  244.                                             //  call on a watch folder that's been modified.
  245.  
  246.                     // May pick up the loop where we left off
  247.         for ( ; gSearchState.theResultIndex < gCSParamRec.ioActMatchCount; gSearchState.theResultIndex++ )
  248.         {
  249.             anFSSpecPtr = &gCSParamRec.ioMatchPtr[gSearchState.theResultIndex];
  250.             aWatchFolderPtr = FolderInList( anFSSpecPtr );
  251.         
  252.             if ( aWatchFolderPtr )
  253.             {
  254.                 StartCatInfo( anFSSpecPtr );    // Start an asychronous PBGetCatInfo() call
  255.                 gSearchState.theResultIndex++;    // Increment for next DoCheckCatSearchResults() call.
  256.                 return DiskAccessSleep;            // Return immediately
  257.             }
  258.             else if ( VolumeAndDirIDInList( anFSSpecPtr->vRefNum, anFSSpecPtr->parID ) )
  259.             {
  260.                 SendChangeEvent( &aPSN, anFSSpecPtr, kTypeFileModified );
  261.                 eventsSent++;
  262.                 if ( eventsSent >= MaxBatchEvents )
  263.                 {
  264.                     gSearchState.theResultIndex++;
  265.                     return DiskAccessSleep;    // Give the target application a bit of time
  266.                 }                            // to catch up with events sent.
  267.             }
  268.         }
  269.     }
  270.  
  271.         // If we get this far then we have finished looking at all the results.
  272.  
  273.     gSearchState.theState = kSearchNextVolume;
  274.     gSearchState.theResultIndex = 0;        // Reset the index for PBCatSearch() results.
  275.  
  276.     result = DoBackgroundTask( );            // Get a time via kSearchNextVolume
  277.                                             // which goes onto kSearchIdle.
  278.  
  279.     return result;
  280. }
  281.  
  282.  
  283. // Update the time searched up to on the current volume.
  284. // Set the next volume to search as the next in the volume list.
  285. // Set the task back to kSearchIdle.
  286.  
  287. long    DoSearchNextVolume( void )
  288. {
  289.     WatchVolumePtr    tempPtr = GetHeadVolumePtr( );
  290.     unsigned long    result = 0;
  291.  
  292.     if ( tempPtr )        // Update the date checked up to
  293.         tempPtr->theLastModCheck = gSearchInfo2.dirInfo.ioDrMdDat;
  294.  
  295.     FirstVolumeToLast( );                    // Cycle around volumes containing watched folders
  296.     gSearchState.theState = kSearchIdle;    // Back to start
  297.     
  298.     result = DoBackgroundTask( );            // Get a time via kSearchIdle
  299.  
  300.     return result;
  301. }
  302.  
  303.  
  304. // Check that we're not in an asychronous call so that we can quit.
  305. // If we were in an asychronous call then the memory we've allocated for
  306. // the results will be disposed of if we quit. This may result in writing
  307. // over someone elses memory.
  308.  
  309. Boolean    CanQuitTask( void )
  310. {
  311.     Boolean        result;
  312.  
  313.     switch ( gSearchState.theState )
  314.     {
  315.         case kWaitForCatSearch:            // Check that search is complete
  316.             result = ( gCSParamRec.ioResult < 0 );
  317.             break;
  318.             
  319.         case kCheckCatSearchResults:    // Check we're not waiting for a PBGetCatInfo()
  320.             result = ( gCInfoPBRec.dirInfo.ioResult <= 0 );
  321.             break;
  322.     
  323.         default:
  324.             result = true;
  325.     }
  326.     
  327.     return result;
  328. }
  329.  
  330.  
  331. // Each cycle must check every volume with watch folders on it. Therefore
  332. // we need to divide up the cycle time by the number of volumes.
  333. // Time returned is in seconds.
  334.  
  335. static unsigned long    GetAverageSleepTime( void )
  336. {
  337.     unsigned long    result = 0;
  338.  
  339.     if ( GetNumberOfVolumes( ) )
  340.     {
  341.         result = GetCycleTime( ) / GetNumberOfVolumes( );
  342.         if ( ! result )                // If this happens we've got alot of volumes to check!
  343.             result = 1;                // Don't want to return a zero.
  344.     }
  345.     else
  346.         result = GetCycleTime( );    // Someone may add a folder - so check every cycle time
  347.     
  348.     return result;
  349. }
  350.  
  351.  
  352. // The creator type of the target application is stored in a 'Targ' resource.
  353. // This routine just grabs the first resource of this type if there is one.
  354.  
  355. static OSErr    GetTargetApplicationCreator( OSType* theCreator )
  356. {
  357.     short            count;
  358.     Handle             aHandle;
  359.     OSErr            result = resNotFound;
  360.     
  361.     count = Count1Resources( kTargetAppResource );
  362.     if ( count )
  363.     {
  364.         aHandle = Get1IndResource( kTargetAppResource, 1 );
  365.         if ( aHandle )
  366.         {
  367.             *theCreator = *(OSType *) *aHandle;
  368.             ReleaseResource( aHandle );
  369.             result = noErr;
  370.         }
  371.     }
  372.     
  373.     return result;
  374. }
  375.  
  376.  
  377. // Check that the application is running. If a pointer to a ProcessSerialNumber
  378. // is given it will be filled in if the routine reutrns true.
  379.  
  380. static Boolean    TargetApplicationRunning( ProcessSerialNumber* thePSN )
  381. {
  382.     ProcessSerialNumber    aPSN;        // Use this if no ProcessSerialNumber supplied
  383.     Boolean                result;
  384.     
  385.     if ( thePSN )
  386.         result = GetCreatorApplicationPSN( gTargetCreator, thePSN );
  387.     else
  388.         result = GetCreatorApplicationPSN( gTargetCreator, &aPSN );
  389.     
  390.     return result;
  391. }
  392.  
  393.  
  394. static Boolean    GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN )
  395. {
  396.     ProcessInfoRec        aProcessInfoRec;
  397.     FSSpec                 processFSSpec;
  398.     Boolean                result = false;
  399.  
  400.         // check the current processes to see if the application is already
  401.         // running, and get its process serial number.
  402.     thePSN->lowLongOfPSN = kNoProcess;
  403.     thePSN->highLongOfPSN = 0;
  404.     
  405.     aProcessInfoRec.processInfoLength = sizeof( FSSpec );
  406.     aProcessInfoRec.processName = NULL;
  407.     aProcessInfoRec.processAppSpec = &processFSSpec;
  408.     
  409.     while ( noErr == GetNextProcess( thePSN ) )
  410.         if ( noErr == GetProcessInformation( thePSN, &aProcessInfoRec ) )
  411.             if ( aProcessInfoRec.processSignature == theCreator )
  412.             {
  413.                 result = true;
  414.                 break;
  415.             }
  416.  
  417.     return result;
  418. }
  419.  
  420.  
  421. // This routine sets off a PBCatSearch() on the given volume to see if any of the
  422. // directories or files have been modified. Directory changes occur when a file or folder is
  423. // added or removed.
  424.  
  425. static void    StartCatSearch( WatchVolumePtr theVolPtr )
  426. {
  427.     GetDateTime( &gSearchState.theLastTime );        // Keep for cycle time updates even
  428.                                                     //  if target isn't running
  429.  
  430.     if ( TargetApplicationRunning( NULL ) )
  431.     {
  432.             // Set up the variable parts of the PBCatSearch() parameter block.
  433.             // Note that some has been set up in InitTask().
  434.  
  435.         gCSParamRec.ioCompletion = NULL;                // Asychronous call with no completion routine
  436.         gCSParamRec.ioVRefNum = theVolPtr->theVolRef;
  437.         
  438.         gSearchInfo1.dirInfo.ioDrMdDat = theVolPtr->theLastModCheck + 1;
  439.         GetDateTime( &gSearchInfo2.dirInfo.ioDrMdDat );    // Up to now
  440.     
  441.             // Searching on the modification date of directories and files
  442.         gCSParamRec.ioSearchBits = fsSBFlMdDat;
  443.         
  444.         gCSParamRec.ioSearchTime = 0;                    // Allow as much time as needed
  445.         gCSParamRec.ioCatPosition.initialize = 0;
  446.         
  447.             // Update the global search state before we call the asynchronous routine
  448.         
  449.         gSearchState.theState = kWaitForCatSearch;         // Where to go next.
  450.                                         
  451.         (void)PBCatSearch( &gCSParamRec, true );        // Do the modification search
  452.     }
  453.     else    // Don't bother accessing the disk - just act like we have done the search
  454.         gSearchState.theState = kSearchNextVolume;
  455. }
  456.  
  457.  
  458. static void    StartCatInfo( FSSpec* theSpec )
  459. {
  460.     gSearchState.theInfoDir = theSpec;        // Set so we know what directory information
  461.                                             //  we're waiting for.
  462.     gCInfoPBRec.dirInfo.ioCompletion = NULL;// Asychronous call with no completion routine
  463.     gCInfoPBRec.dirInfo.ioNamePtr = theSpec->name;
  464.     gCInfoPBRec.dirInfo.ioVRefNum = theSpec->vRefNum;
  465.     gCInfoPBRec.dirInfo.ioDrDirID = theSpec->parID;
  466.     gCInfoPBRec.dirInfo.ioFDirIndex = 0;    // Use ioNamePtr and ioDirID
  467.     gCInfoPBRec.dirInfo.ioACUser = 0;        // If this does not compile try using
  468.                                             // thePB->dirInfo.filler2 or thePB->dirInfo.ioACUser
  469.                                             // Clear it before calling PBGetCatInfo()
  470.     (void)PBGetCatInfo( &gCInfoPBRec, true );
  471. }
  472.  
  473.  
  474. // Check that a PBGetCatInfo() was beng waited for. if it was and it's completed
  475. // then send off change event to target application.
  476. // This routine will return false if the result of the PBGetCatInfo() is still being waited for.
  477.  
  478. static    Boolean    CheckCatInfoResult( ProcessSerialNumber* thePSN )
  479. {
  480.     WatchFolderPtr        aWatchFolderPtr;
  481.     
  482.     if ( ! gSearchState.theInfoDir )            // No PBGetCatInfo() being waited for.
  483.         return true;
  484.     
  485.     if ( gCInfoPBRec.dirInfo.ioResult > 0 )        // Still waiting for PBGetCatInfo()
  486.         return false;
  487.     
  488.     aWatchFolderPtr = FolderInList( gSearchState.theInfoDir );
  489.  
  490.     if ( aWatchFolderPtr && noErr == gCInfoPBRec.dirInfo.ioResult )
  491.     {
  492.         if ( aWatchFolderPtr->theFileCount < gCInfoPBRec.dirInfo.ioDrNmFls )
  493.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileAdded );
  494.         else if ( aWatchFolderPtr->theFileCount > gCInfoPBRec.dirInfo.ioDrNmFls )
  495.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileRemoved );
  496.         else    // Changed but we don't know whether modified or not
  497.             SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileModified );
  498.         
  499.         aWatchFolderPtr->theFileCount = gCInfoPBRec.dirInfo.ioDrNmFls;
  500.     }
  501.     
  502.     gSearchState.theInfoDir = NULL;    // This is set to tell us we are waiting for the PBGetCatInfo()
  503.  
  504.     return true;
  505. }
  506.